package org.pb.user.service.impl;

import com.alibaba.fastjson.JSONObject;
import org.apache.ibatis.mapping.ResultMap;
import org.mockito.internal.util.StringUtil;
import org.pb.basic.constant.Constant;
import org.pb.basic.constant.WxConstants;
import org.pb.basic.service.impl.BaseServiceImpl;
import org.pb.basic.utils.*;
import org.pb.user.domain.LoginInfo;
import org.pb.user.domain.User;
import org.pb.user.domain.WxUser;
import org.pb.user.dto.UserDto;
import org.pb.user.mapper.LoginInfoMapper;
import org.pb.user.mapper.UserMapper;
import org.pb.user.mapper.WxUserMapper;
import org.pb.user.query.UserQuery;
import org.pb.user.service.IUserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import sun.security.provider.MD5;

import java.awt.print.PrinterAbortException;
import java.io.Console;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 用户表对应的业务层接口实现类
 * @author pb
 */
@Service
public class UserServiceImpl extends BaseServiceImpl<User, UserQuery> implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private LoginInfoMapper loginInfoMapper;

    @Autowired
    private WxUserMapper wxUserMapper;

    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate redisTemplate;

    @Override
    @Transactional
    public void userRegister(UserDto userDto) {
        // 判断参数中所有属性都不能为空
        if (StringUtils.isEmpty(userDto.getPhone()) || StringUtils.isEmpty(userDto.getVerifyCode())
            || StringUtils.isEmpty(userDto.getPassword()) || StringUtils.isEmpty(userDto.getRePassword())) {
            // 如果有一个参数为空，则抛出异常
            throw new RuntimeException("参数不能为空，请全部填写");
        }
        // 判读两次密码是否一致，如果不一致则抛出异常
        if (!userDto.getPassword().equals(userDto.getRePassword())) {
            throw new RuntimeException("两次密码不一致，请重新填写");
        }
        // 获取验证码
        Object obj = redisTemplate.opsForValue().get(Constant.REGISTER_CODE_PREFIX + userDto.getPhone());
        // 判断验证码是否存在
        if (obj == null) {
            // 如果obj为空，则表示验证码过期或者验证码不存在
            throw new RuntimeException("验证码失效，请重新获取");
        }
        // 获取到redis中的验证码
        String codeTmp = obj.toString().split(":")[0];
        // 判断传入的验证码和redis中保存的验证码是否一致，如果不一致，则表示验证码输入错误,使用equalsIgnoreCase忽略大小写
        if (!userDto.getVerifyCode().equalsIgnoreCase(codeTmp)) {
            throw new RuntimeException("验证码错误，请正确填写");
        }
        // 为了防止用户通过手机号在验证码有效时间内重复注册，满足条件时，先删除对应验证码，再添加用户信息。
        redisTemplate.delete(Constant.REGISTER_CODE_PREFIX + userDto.getPhone());
        // 如果条件都满足的话，就添加数据，获取到对应的user和loginInfo对象
        User user = UserDto2User(userDto);
        LoginInfo loginInfo = User2LoginInfo(user);
        // 将loginInfo的数据设置为user中的loginInfo属性值
        user.setLoginInfo(loginInfo);
        // 优先保存LoginInfo数据，因为user需要loginInfo的id，因此先添加LoginInfo，获取自增长id
        loginInfoMapper.save(user.getLoginInfo());
        // 将用户数据保存到用户表中
        userMapper.save(user);
    }

    @Override
    public AjaxResult bind(Map<String, String> map) {
        // 获取到map中关于手机号和微信用户openid和授权token、还有验证码
        String phone = map.get("phone");
        String access_token = map.get("accessToken");
        String openid = map.get("openId");
        String verigyCode = map.get("verifyCode");
        // 先判断验证码是否正确
        String code = redisTemplate.opsForValue().get(Constant.BINDER_CODE_PREFIX + phone).toString();
        // 如果code不存在或者不等于verifyCode那么表示，验证码错误，抛出异常。否则，正确继续执行绑定操作。
        if (code == null || !code.split(":")[0].equals(verigyCode)) {
            throw new RuntimeException("验证码无效，请重新填写");
        }
        // 如果手机号为空，抛出异常，表示手机号不能为空
        if (phone == null) {
            throw new RuntimeException("手机号不能为空");
        }
        // 如果access_token、openid中有一个值为空，则表示参数不全，需要用户填写
        if (access_token == null || openid == null) {
            throw new RuntimeException("参数异常");
        }
        // 根据手机号获取到用户信息
        User user = userMapper.getByPhone(phone);
        /**
         * 1. 如果用户信息为空，则表示没有该手机号注册用户，直接创建该用户，并同步到loginInfo中
         * 2. 如果有用户信息，则需要判断用户是否绑定了微信账号：
         *  2.1 联表查询，如果通过user_id获取到wxUser的数据，表示已经绑定了微信用户，则绑定失败，提示已经绑定了微信。
         *  2.2 联表查询，如果通过user_id没有获取到wxUser的数据，表示没有绑定微信用户，创建微信用户，并绑定user，同步数据到user
         * 3. 绑定数据后，需要创建一个4位有效验证码，让用户在三分钟内完成绑定登陆
         */
        User userTmp = null;

        // 判断用户数据是否不为空
        if (user != null) {
            // 如果用户数据不为空，判断查询对应wxUser表中是否有关联user的微信账号.获取微信账号
            WxUser wxUserTmp = wxUserMapper.getByUserId(user.getId());

            // 判断userTmp是否不为空
            if (wxUserTmp != null) {
                // 如果userTmp不为空，那么表示该手机号已经绑定了微信账号，不能在绑定了
                throw new RuntimeException("该手机号已经绑定了微信账号，请重新填写或用绑定微信账号扫码登陆");
            }
            // 如果wxUserTmp为空的话，表示没有绑定微信账号可以直接创建微信账号并同步到user中
            userTmp = userMapper.loadById(user.getId());
        } else {
            // 如果user数据为空，表示当前没有创建绑定平台用户账号，直接创建对应的平台用户，并同步到loginInfo中
            LoginInfo loginInfo = new LoginInfo();
            userTmp = new User();
            userTmp.setUsername(phone);
            userTmp.setPhone(phone);
            // 通过工具类生成一个6位初始密码
            String password = StrUtils.getRandomString(6);
            // 获取盐值
            String salt = StrUtils.getComplexRandomString(32);
            // 将密码和盐值拼接成一个新密码
            password = password + salt;
            // 将密码进行加密处理
            password = MD5Utils.encrypByMd5(password);
            // 设置盐值和密码
            userTmp.setSalt(salt);
            userTmp.setPassword(password);
            // 然后将user的值映射转化到loginInfo中
            userTmp.setLoginInfo(User2LoginInfo(userTmp));
            // 先添加loginInfo
            loginInfoMapper.save(userTmp.getLoginInfo());
            // 然后添加user
            userMapper.save(userTmp);
            // 将用户id同步到对应的微信用户中
        }
        // 创建一个wxUser对象
        WxUser wxUser = new WxUser();
        // 通过openid和access_token获取到对应的微信用户资源【access_token就是资源访问授权令牌】
        String url = WxConstants.GET_USER_URL.replace("ACCESS_TOKEN", access_token).replace("OPENID", openid);
        String result = HttpClientUtils.httpGet(url);
        // 通过fastJson解析得到的json字符串结果
        JSONObject jsonObject = JSONObject.parseObject(result);
        // 通过调用getString方法获取到对应json中的参数值，并设置到wxUser中
        wxUser.setOpenid(openid);
        wxUser.setNickname(jsonObject.getString("nickname"));
        wxUser.setSex(jsonObject.getInteger("sex"));
        wxUser.setUnionid(jsonObject.getString("unionid"));
        wxUser.setHeadimgurl(jsonObject.getString("headimgurl"));
        // 取出国、省、市对应值，并拼接成address字符串
        String address = jsonObject.getString("country") + jsonObject.getString("province") + jsonObject.getString("city");
        wxUser.setAddress(address);
        System.out.println(wxUser);
        // 将userid设置到wxUser中
        wxUser.setUser_id(userTmp.getId());
        // 保存WxUser的数据
        wxUserMapper.save(wxUser);
        // 绑定成功即表示成功登陆，获取登陆token
        String token = UUID.randomUUID().toString();
        // 获取到登陆信息
        LoginInfo loginInfo = loginInfoMapper.getByUserId(wxUser.getUser_id());
        // 清空盐值和密码
        loginInfo.setPassword(null);
        loginInfo.setSalt(null);
        // 将对应的数据保存到Redis中
        redisTemplate.opsForValue().set(token, loginInfo, 30, TimeUnit.MINUTES);
        // 创建一个hashMap保存登陆信息
        Map<String, Object> resutlMap = new HashMap<>();
        resutlMap.put("token", token);
        resutlMap.put("loginInfo", loginInfo);
        // 返回登陆数据
        return AjaxResult.successWithData(resutlMap);
    }

    /**
     * 将UserDto对象转化为User对象
     * @param userDto 要转化的UserDto对象
     * @return 返回获取到了UserDto对象属性值的User对象
     */
    private User UserDto2User(UserDto userDto) {
        User user = new User();
        user.setUsername(userDto.getPhone());
        user.setPhone(userDto.getPhone());
        // 创建盐值
        String salt = StrUtils.getComplexRandomString(32);
        // 将盐值和密码拼接到一起
        String password = userDto.getPassword() + salt;
        // 通过MD5进行密码加密
        password = MD5Utils.encrypByMd5(password);
        // 将盐值和密码添加到user对象中
        user.setSalt(salt);
        user.setPassword(password);
        return user;
    }

    /**
     * 将user对象转化为LoginInfo对象
     * @param user 要转化的User对象
     * @return 返回获取到User对象中参数的LoginInfo对象
     */
    private LoginInfo User2LoginInfo(User user) {
        // 创建一个LoginInfo对象
        LoginInfo loginInfo = new LoginInfo();
        // 通过bean工具类将user中的对应参数复制到loginInfo中
        BeanUtils.copyProperties(user, loginInfo);
        return loginInfo;
    }
}
